package ygg.encoding.swf.tag.codec
{
	import j.sus2maj7.encoding.*;
	import j.sus2maj7.context.*;
	
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	
	
	import ygg.encoding.swf.abc.*;
	import ygg.encoding.swf.abc.object.*;
	import ygg.encoding.swf.abc.constant.*;
	
	import ygg.encoding.swf.tag.*;
	import ygg.encoding.swf.tag.object.DoABC2;
	import ygg.encoding.swf.tag.object.DoABC;
	
	public class DoABCCodec extends TagCodec
	{
		//*
		//public var major:int
		//public var minor:int
		
		
		//public var defaults:Array = new Array(Constants.constantKinds.length)

		
		private var publicNs:TypedNamespace;
		private var anyNs:TypedNamespace;
		
		//public var magic:int
		
		//added
		//protected var metadata:Array;
		//*/
		
		public function DoABCCodec()
		{
			super();
			publicNs = new TypedNamespace();
			publicNs.ns = new Namespace("");
			publicNs.type = NamespaceKind.NAMESPACE_;
			
			anyNs = new TypedNamespace();
			anyNs.ns = new Namespace("*");
			anyNs.type = NamespaceKind.NAMESPACE_;
		}
		
		override public function decode(binary:IBinary, context:IContext=null):*
		{
			var abc:DoABC = new DoABC();
			var b:TagBinary = binary as TagBinary;
			b.position = 0;
			b.syncBits();
			
			trace('doabccode');
			
			//look for magic
			var magic:int = b.readInt();
			trace("magic " + magic.toString(16) + "\n");
			if (magic != (46 << 16 | 14) && magic != (46 << 16 | 15) && magic != (46 << 16 | 16))
			{
				throw new Error("not an abc file.  magic=" + magic.toString(16));
			}
			
			var ctx:AbcContext = new AbcContext();
			ctx.constantPool = decodeConstantPool(b);
			
			
			
			ctx.methods = decodeMethods(b, ctx);
			
			
			ctx.metadata = decodeMetadata(b, ctx);
			ctx.instances = decodeInstances(b, ctx);
			ctx.classes = decodeClasses(b, ctx);
			ctx.scripts = decodeScripts(b, ctx);
			//ctx.methodBodies =
			decodeMethodBodies(b, ctx);
			
			for each (var c:Class_ in ctx.classes)
			{
				for each (var t:Trait in c.members)
				{
					t.isStatic = true;
				}
			}
			
			
			//abc.scripts = ctx.constantPool.scripts;
			//abc.methods = ctx.constantPool.methods;
			abc.context = ctx;
			
			trace('---');
			return abc;
		}
		
		protected function decodeConstantPool(b:TagBinary):ConstantPool
		{
			var i:int, j:int;
			var n:int;
			var kind:int;

			var ints:Array;
			var uints:Array;
			var doubles:Array;
			var strings:Array;
			var namespaces:Array;
			var nsSets:Array;
			var multinames:Array;
			//var start:int = b.position;
			
			// ints
			n = b.readEncU32();
			ints = [0];
			
			for (i = 1; i < n; i++)
			{
				ints[i] = b.readEncU32();
			}
				
			// uints
			n = b.readEncU32();
			uints = [0];
			
			for (i = 1; i < n; i++)
			{
				uints[i] = uint(b.readEncU32());
			}
				
			// doubles
			n = b.readEncU32();
			doubles = [NaN];
			
			for (i = 1; i < n; i++)
			{
				doubles[i] = b.readDouble();
			}

			// strings
			n = b.readEncU32();
			strings = [""];
			
			for (i = 1; i < n; i++)
			{
				strings[i] = b.readUTFBytes( b.readEncU32() );
			}

			// namespaces
			n = b.readEncU32();
			namespaces = [publicNs]; //JF should this be anyNs?
			var u:uint
			var tn:TypedNamespace;
			
			for (i = 1; i < n; i++)
			{
				u = b.readByte()
				switch (u)
				{
					case NamespaceKind.NAMESPACE_:
					case NamespaceKind.PACKAGE_NAMESPACE:
					case NamespaceKind.PACKAGE_INTERNAL_NS:
					case NamespaceKind.PROTECTED_NAMESPACE:
					case NamespaceKind.EXPLICIT_NAMESPACE:
					case NamespaceKind.STATIC_PROTECTED_NS:
						{
							tn = new TypedNamespace();
							tn.type = u;
							tn.ns = new Namespace(strings[b.readEncU32()]);
							namespaces[i] = tn;
							break;
						}
					case NamespaceKind.PRIVATE_NS:
						b.readEncU32(); //JF: why not saved? -> apparently its a number
						tn = new TypedNamespace();
						tn.type = u;
						tn.ns = new Namespace(null, "private");
						namespaces[i] = tn;
						
						break;
				}
			}
			
			// namespace sets
			n = b.readEncU32();
			nsSets = [null];
			
			for (i=1; i < n; i++)
			{
				var count:int = b.readEncU32();
				var nsset:Array = nsSets[i] = [];
				
				for (j = 0; j < count; j++)
				{
					nsset[j] = namespaces[b.readEncU32()];
				}
			}

			// multinames
			n = b.readEncU32();
			multinames = [null];
			namespaces[0] = anyNs;
			strings[0] = "*"; // any name
			var mn:Multiname;
			trace(n);
			for (i = 1; i < n; i++)
			{
				switch (b.readByte())
				{
					case MultinameKind.QNAME:
					case MultinameKind.QNAME_A:
						mn = new Multiname();
						mn.namespaces = [];
						mn.namespaces.push(namespaces[b.readEncU32()]);
						mn.name = strings[b.readEncU32()];
						multinames[i] = mn;
						break;
					
					case MultinameKind.RTQNAME:
					case MultinameKind.RTQNAME_A:
						mn = new Multiname();
						mn.name = strings[b.readEncU32()];
						multinames[i] = mn;
						break;
					
					case MultinameKind.RTQNAME_L:
					case MultinameKind.RTQNAME_LA:
						multinames[i] = null
						break;
					//not in spec
					case MultinameKind.NAME_L:
					case MultinameKind.NAME_LA:
						mn = new Multiname();
						mn.namespaces = [];
						mn.namespaces.push(new Namespace(""));
						mn.name = null;
						multinames[i] = mn;
						break;
					
					case MultinameKind.MULTINAME:
					case MultinameKind.MULTINAME_A:
						mn = new Multiname();
						mn.name = strings[b.readEncU32()];
						mn.namespaces = nsSets[b.readEncU32()];
						
						multinames[i] = mn;
						break;

					case MultinameKind.MULTINAME_L:
					case MultinameKind.MULTINAME_LA:
						var mn2:Multiname = new Multiname();
						mn2.namespaces = nsSets[b.readEncU32()];
						mn2.name = null;
						multinames[i] = mn2;
						break;
						
					default:
						throw new Error("invalid kind " + b[b.position-1])
				}
			}
			
			namespaces[0] = publicNs;
			strings[0] = "*";
			
			var cp:ConstantPool = new ConstantPool();
			cp.ints = ints;
			cp.uints = uints;
			cp.doubles = doubles;
			cp.strings = strings;
			cp.namespaces = namespaces;
			cp.nsSets = nsSets;
			cp.multinames = multinames;
			
			return cp;
		}
		
		protected function decodeMethods(b:TagBinary, ctx:AbcContext):Vector.<Method>
		{
			//var start:int = b.position;
			
			var multinames:Array = ctx.constantPool.multinames;
			var strings:Array = ctx.constantPool.strings;
			
			//multinames[0] = new QName(publicNs, "*");
			var mn:Multiname = new Multiname();
			mn.name = '*';
			mn.namespaces = [];
			mn.namespaces.push(publicNs);
			multinames[0] = mn;
			
			var numMethods:int = b.readEncU32();
			var methods:Vector.<Method> = new Vector.<Method>();
			
			for (var i:int=0; i < numMethods; i++)
			{
				var mi:Method = new Method();
				methods[i] = mi;
				
				var numParams:int = b.readEncU32();
				
				mi.returnType = multinames[b.readEncU32()];
				
				if (numParams > 0)
				{
					mi.params = new Vector.<Parameter>();
					
					
					
					for (var j:int = 0; j < numParams; j++)
					{
						var p:Parameter = new Parameter();
						p.type = multinames[b.readEncU32()]; //0=*type
						mi.params[j] = p;
					}
				}
				
				mi.debugName = strings[b.readEncU32()]; //why debug name and not name?
				var flags:uint = b.readByte();
				
				mi.needsArguments = (flags & MethodFlag.NEED_ARGUMENTS) > 0;
				mi.needsActivation = (flags & MethodFlag.NEED_ACTIVATION) > 0;
				mi.needsRest = (flags & MethodFlag.NEED_REST) > 0;
				mi.hasOptional = (flags & MethodFlag.HAS_OPTIONAL) > 0;
				mi.setDxns = (flags & MethodFlag.SET_DXNS) > 0;
				mi.hasParamNames = (flags & MethodFlag.HAS_PARAM_NAMES) > 0;
				
				if (numParams > 0)
				{
					if (mi.hasOptional)
					{
						// has_optional
						var numOptions:int = b.readEncU32();
						
						for( var k:int = numParams-numOptions; k < numParams; ++k)
						{
							var index:int = b.readEncU32();    // optional value index
							var kind:int = b.readByte() // kind byte for each default value
							
							Parameter(mi.params[k]).value = ctx.constantPool.getPool(kind)[index];
							
						}
					}
					
					if (mi.hasParamNames)
					{
						// has_paramnames
						for( var l:int = 0; l < numParams; ++l)
						{
							Parameter(mi.params[l]).name = strings[ b.readEncU32() ];
							
						}
					}
					
				}
			}
			
			return methods;
			//trace("Method count " +numMethods+ " size "+(b.position-start)+" "+int(100*(b.position-start)/b.length)+" %\n")
		}

		protected function decodeMetadata(b:TagBinary, ctx:AbcContext):Vector.<Metadata>
		{
			var strings:Array = ctx.constantPool.strings;
			
			var count:int = b.readEncU32();
			var metadata:Vector.<Metadata> = new Vector.<Metadata>();
			
			for (var i:int=0; i < count; i++)
	        {
				// MetadataInfo
				var mi:Metadata = new Metadata();
				metadata[i] = mi;
				
				mi.name = strings[b.readEncU32()];
				
	            var numItems:int = b.readEncU32();
	            var itemNames:Array = [];
				
	            for (var q:int = 0; q < numItems; ++q)
				{
					itemNames[q] = strings[b.readEncU32()]; // name
				}
				
				mi.items = {};
				for (var r:int = 0; r < numItems; ++r)
				{
					mi.items[itemNames[r]] = strings[b.readEncU32()]; // value
				}
			}
			
			return metadata;
		}

		protected function decodeInstances(b:TagBinary, ctx:AbcContext):Vector.<Instance>
		{trace('pii');
			var multinames:Array = ctx.constantPool.multinames;
			var namespaces:Array = ctx.constantPool.namespaces;
			
			var methods:Vector.<Method> = ctx.methods;
			
			//var start:int = b.position;
			var count:int = b.readEncU32();
			var instances:Vector.<Instance> = new Vector.<Instance>();
			
			for (var i:int=0; i < count; i++)
	        {
	        	var instance:Instance = new Instance();
				instances[i] = instance;
				
	        	instance.multiname = multinames[b.readEncU32()];
				if (instance.multiname)
					parseMultiname(instance);
				
				instance.superMultiname = multinames[b.readEncU32()];
				
	        	var flags:uint = b.readByte();
				
				instance.isSealed = (flags & InstanceFlag.CLASS_SEALED) > 0;
				instance.isFinal = (flags & InstanceFlag.CLASS_FINAL) > 0;
				instance.isInterface = (flags & InstanceFlag.CLASS_INTERFACE) > 0;
				instance.usesProtected = (flags & InstanceFlag.CLASS_PROTECTED_NS_) > 0;
				
				
				if (flags & InstanceFlag.CLASS_PROTECTED_NS_)
				{
					instance.protectedNs = TypedNamespace(namespaces[b.readEncU32()]).ns;
				}
	
				var numInterfaces:int = b.readEncU32();
				instance.interfaces = [];
				for (var j:int = 0; j < numInterfaces; j++)
				{
	        		instance.interfaces[i] = multinames[b.readEncU32()];
				}
				
	        	var m:Method = methods[b.readEncU32()];
				instance.init = m;
				m.isConstructor = true;
	        	m.multiname = instance.multiname; //instance constructor
	        	//m.kind = TraitKind.METHOD;
	        	//m.id = -1; //not sure
				parseMultiname(m);
				
				//trace(m.qName);
	        	decodeTraits(instance, b, ctx );
				//trace('-');
	        }
			
			return instances;
		}
		public function decodeTraits(container:ITraitContainer, b:TagBinary, ctx:AbcContext):void
		{
			var multinames:Array = ctx.constantPool.multinames;
			var methods:Vector.<Method> = ctx.methods;
			var metadata:Vector.<Metadata> = ctx.metadata;
			var classes:Vector.<Class_> = ctx.classes; //null until decode classes which is after instances
			
			var numTraits:int = b.readEncU32();
			//trace('numTraits', numTraits);
			for (var i:int=0; i < numTraits; i++)
			{
				var name:* = multinames[b.readEncU32()];
				var tag:int = b.readByte();
				var kind:int = tag & 0xf;
				var t:Trait;
				var id:uint;
				
				var sc:ISlotContainer;
				var mc:IMethodContainer;
				//trace(name, kind);
				if (kind == TraitKind.SLOT || kind ==  TraitKind.CONST)
				{
					if (kind == TraitKind.SLOT)
					{
						t = new Variable();
					}
					else
					{
						t = new Constant();
					}
					
					var s:Slot = t as Slot;
					sc = ISlotContainer(container);
					
					id = b.readEncU32();
					s.id = id;
					if (id == 0)
					{
						sc.slots[sc.slots.length] = s;
					}
					else
					{
						if (sc.slots.length <= id)
						{
							sc.slots.length = id+1;
							sc.slots[id] = s;
						}
						else
						{
							if (sc.slots[id] == null) sc.slots[id] = s;
							else
							{
								trace('SLOT ALREADY TAKEN! -> appending to end');
								sc.slots[sc.slots.length] = s;
							}
						}
					}
					
					s.type = multinames[b.readEncU32()];
					
					var index:int = b.readEncU32();
					if (index)
					{
						s.value = ctx.constantPool.getPool(b.readByte())[index];
					}
					else
					{
						s.value = undefined
					}
					
					if (kind == TraitKind.SLOT)
					{
						sc.variables.push(t);
					}
					else
					{
						sc.constants.push(t);
					}
				}
				else if (kind == TraitKind.CLASS) //should only happen with Script
				{
					id = b.readEncU32();
					t = classes[b.readEncU32()];
					var c:Class_ = t as Class_;
					sc = ISlotContainer(container);
					
					c.id = id;
					if (id == 0)
					{
						sc.slots[sc.slots.length] = c;
					}
					else
					{
						if (sc.slots.length <= id)
						{
							sc.slots.length = id+1;
							sc.slots[id] = c;
						}
						else
						{
							if (sc.slots[id] == null) sc.slots[id] = c;
							else
							{
								trace('SLOT ALREADY TAKEN! -> appending to end');
								sc.slots[sc.slots.length] = c;
							}
						}
					}
					
					IClassContainer(container).classes.push(c);
				}
				else if (kind == TraitKind.FUNCTION)
				{//trace('fun');
					t = new Function_();
					var f:Method = t as Method;
					sc = ISlotContainer(container);
					id = b.readEncU32();
					f.id = id;
					if (id == 0)
					{
						sc.slots[sc.slots.length] = f;
					}
					else
					{
						if (sc.slots.length <= id)
						{
							sc.slots.length = id+1;
							sc.slots[id] = f;
						}
						else
						{
							if (sc.slots[id] == null) sc.slots[id] = f;
							else
							{
								trace('SLOT ALREADY TAKEN! -> appending to end');
								sc.slots[sc.slots.length] = f;
							}
						}
					}
					
					f = methods[b.readEncU32()];
					
					//add to functions
					IFunctionContainer(container).functions.push(f);
				}
				else if (kind == TraitKind.METHOD || kind == TraitKind.GETTER || kind == TraitKind.SETTER)
				{//trace('meth');
					id = b.readEncU32();
					t = methods[b.readEncU32()];
					
					var m:Method = Method(t);
					
					
					m.id = id;
					if (kind == TraitKind.GETTER) m.isGetter = true;
					else if (kind == TraitKind.SETTER) m.isSetter = true;
					
					mc = IMethodContainer(container);
					
					if (id == 0)
					{
						mc.methods[mc.methods.length] = m;
					}
					else
					{
						if (mc.methods.length <= id)
						{
							mc.methods.length = id+1;
							mc.methods[id] = m;
						}
						else
						{
							if (mc.methods[id] == null)
							{
								mc.methods[id] = m;
							}
							else
							{
								trace('methods disp_id ALREADY TAKEN! -> appending to end');
								mc.methods[mc.methods.length] = m;
							}
						}
					}
					
				}
				//trace('b');
				//t.kind = kind;
				t.multiname = name;
				parseMultiname(t);
				container.members[i] = t;
				container.names[String(name)] = container.members[i]; //name reference, needed?
				
				tag >>= 4;
	            if ( tag & TraitAttribute.METADATA )
				{
					t.metadata = [];
					
					for (var j:int = 0, mdCount:int = b.readEncU32(); j < mdCount; ++j)
					{
						t.metadata[j] = metadata[b.readEncU32()];
					}
				}
				if ( tag & TraitAttribute.FINAL ) m.isFinal = true;
				if ( tag & TraitAttribute.OVERRIDE ) m.isOverride = true;
			}
			
		}
		
		protected function decodeClasses(b:TagBinary, ctx:AbcContext):Vector.<Class_>
		{
			trace('pci');
			//needs instances and methods
			var instances:Vector.<Instance> = ctx.instances;
			var methods:Vector.<Method> = ctx.methods;
			
			var count:int = instances.length;
			var classes:Vector.<Class_> = new Vector.<Class_>();// ctx.constantPool.classes = [];
			
			for (var i:int=0; i < count; i++)
	        {
	        	var t:Class_ = new Class_();
				classes[i] = t;
	        	t.init = methods[b.readEncU32()]; //staticInit
	        	t.superQName = "Class";
	        	t.instance = instances[i];
	        	//t.qName = t.instance.qName + "$"; //will be reset later + "$cinit"
				
	        	t.init.multiname = t.instance.multiname;
				parseMultiname(t.init);
	        	//t.ini.kind = TraitKind.METHOD;
				t.init.isStatic = true;
				t.init.isConstructor = true;
				//statics
				
	        	decodeTraits(t, b, ctx );
				//trace('-');
			}
			
			return classes;
		}

		protected function decodeScripts(b:TagBinary, ctx:AbcContext):Vector.<Script>
		{trace('psi');
			//needs multinames methods
			var multinames:Array = ctx.constantPool.multinames;
			var methods:Vector.<Method> = ctx.methods;
			
			//var start:int = b.position;
			var count:int = b.readEncU32();
			
			var scripts:Vector.<Script> = new Vector.<Script>();
			//scripts = ctx.constantPool.scripts = [];
			
			for (var i:int=0; i < count; i++)
	        {
	        	var t:Script = new Script();
				
				scripts[i] = t;
				
	        	t.qName = "script_" + i;
	        	t.name = "script_" + i;
	        	t.packageName = '';
				
	        	//t.superMultiname = multinames[0]; // Object
	        	t.init = methods[b.readEncU32()];
	        	//t.init.qName = t.qName + "$init";
				t.init.qName = t.qName + "$init";
				t.init.name = t.name + "$init";
				t.init.packageName = t.packageName;
				t.init.accessSpecifier = AccessSpecifier.INTERNAL;
				
				t.init.isStatic = true;
				t.init.isConstructor = true;
				//t.ini.kind = TraitKind.METHOD;
				
	        	decodeTraits(t, b, ctx );
	        }
			
			return scripts;
			//trace("ScriptInfo size "+(b.position-start)+" "+int(100*(b.position-start)/b.length)+" %\n")
		}

		protected function decodeMethodBodies(b:TagBinary, ctx:AbcContext):Vector.<MethodBody>
		{trace('pmb');
			//needs methods multinames
			var methods:Vector.<Method> = ctx.methods;
			var multinames:Array = ctx.constantPool.multinames;
			
			//var start:int = b.position;
			var count:int = b.readEncU32();
			
			var methodBodies:Vector.<MethodBody> = new Vector.<MethodBody>();
			
			for (var i:int=0; i < count; i++)
	        {
	        	var mi:Method = methods[b.readEncU32()];
				
				var mb:MethodBody = new MethodBody();
				methodBodies[i] = mb;
				
				//mb.method = methods[b.readEncU32()];
				//mb.method.methodBody = mb;
				mi.body = mb;
				
				mb.maxStack = b.readEncU32();
	        	mb.numLocals = b.readEncU32();
				
	        	mb.initScopeDepth = b.readEncU32();
	        	mb.maxScopeDepth = b.readEncU32();
				
	        	mb.localScopeSize = mb.maxScopeDepth - mb.initScopeDepth;
	
				var codeLength:int = b.readEncU32();
	
				var code:ByteArray = new ByteArray();
				code.endian = Endian.LITTLE_ENDIAN;
				
	        	if (codeLength > 0)
				{
		        	b.readBytes(code, 0, codeLength);
					mb.binary = new AbcBinary(code);
					
				}
				
	       		var numExceptions:int = b.readEncU32();
				mb.exceptions = new Vector.<Exception>();
				
				
	       		for (var j:int = 0; j < numExceptions; j++)
	       		{
					
					var exc:Exception = new Exception();
					
	       			exc.from = b.readEncU32();
	       			exc.to = b.readEncU32();
	       			exc.target = b.readEncU32();
	       			exc.type = multinames[b.readEncU32()];  //JRF:spec says strings!
					//trace("magic " + magic.toString(16))
					//if (magic >= (46<<16|16))
					exc.name = multinames[b.readEncU32()];
					trace("EXCEPTIONS!!! YAYA "+ mi.name+ ' '+exc.type+' '+ exc.name);
					mb.exceptions[j] = exc;
	       		}
				
				mb.activation = new MethodBody(); //JRF: why activation and not traits
				
	       		decodeTraits(mb.activation, b, ctx);
	        }
			
			return methodBodies;
		}
		
		protected function parseMultiname(t:Object):void
		{
			var p:String = '';
			var c:String = '';
			var q:String = '';
			
			p = Multiname(t.multiname).namespaces[0].ns || '';
			c = Multiname(t.multiname).name || '';
			q = p + '::' + c;
			
			t.qName = q;
			t.packageName = p;
			t.name = c;
			
			var ac:String;
			var u:uint = Multiname(t.multiname).namespaces[0].type;
			
			switch (u)
			{
				case NamespaceKind.PRIVATE_NS: //05
					ac = AccessSpecifier.PRIVATE;
					break;
				case NamespaceKind.NAMESPACE_: //08
					ac = '';
					break;
				case NamespaceKind.PACKAGE_NAMESPACE: //22
					ac = AccessSpecifier.PUBLIC;
					break;
				case NamespaceKind.PACKAGE_INTERNAL_NS: //23
					ac = AccessSpecifier.INTERNAL;
					break;
				case NamespaceKind.PROTECTED_NAMESPACE: //24
					ac = AccessSpecifier.PROTECTED;
					break;
				case NamespaceKind.EXPLICIT_NAMESPACE: //25
					ac = u.toString(16);
					break;
				case NamespaceKind.STATIC_PROTECTED_NS: //26
					ac = AccessSpecifier.PROTECTED;
					break;
				default:
					ac = u.toString(16);
					break;
			}
			
			t.accessSpecifier = ac;
			
			
		}
		
		
		override public function encode(o:*, context:IContext=null):IBinary
		{
			var b:TagBinary = new TagBinary();
			var obj:DoABC = o as DoABC;
			
			
			
			return b;
		}
	}
}